package org.beynet.jsonbinding.parser.types.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.beynet.jsonbinding.parser.JSONParsingException;
import org.beynet.jsonbinding.parser.JSONParsingExceptionUnexpectedContent;
import org.beynet.jsonbinding.parser.JSONParsingExceptionUnexpectedEndOfStream;

public abstract class JSONAbstractObject {

    public JSONAbstractObject(String encoding) {
        this.next = -1;
        this.encoding = encoding;
    }

    /**
     * @param is
     * @return the first char which is not a blank char
     * @throws IOException
     * @throws JSONParsingException
     */
    protected int consumeSpaces(InputStream is) throws IOException, JSONParsingException {
        int r = 0;
        while (true) {
            r = is.read();
            if (!(r == ' ' || r == '\t' || r == '\n' || r == '\r')) {
                return r;
            }
        }
    }

    /**
     * read the content of a string : stop when a " is found (not prefixed with
     * a \)
     * 
     * @param is
     * @return
     * @throws IOException
     * @throws JSONParsingExceptionUnexpectedEndOfStream
     */
    protected String readStringContent(InputStream is) throws IOException, JSONParsingExceptionUnexpectedEndOfStream {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        boolean previousIsBackQuote = false;
        int r = 0;
        while (true) {
            r = is.read();
            if (r == -1) {
                throw new JSONParsingExceptionUnexpectedEndOfStream();
            }
            if (r == '"' && previousIsBackQuote == false) {
                return (os.toString(getEncoding()));
            }
            if (previousIsBackQuote==true || r!='\\') os.write(r);
            if (previousIsBackQuote==false && r=='\\') previousIsBackQuote = true;
            else previousIsBackQuote = false;
        }
    }

    /**
     * read a json value ie :
     * <ul>
     * <li>string</li>
     * <li>number</li>
     * <li>object</li>
     * <li>array</li>
     * <li>true</li>
     * <li>false</li>
     * <li>null</li></ul>
     * 
     * @param is
     * @return
     * @throws IOException
     * @throws JSONParsingException
     */
    protected Object readValue(InputStream is) throws IOException, JSONParsingException {
        int r;
        if (next != -1) {
            r = next;
            next = -1;
        } else
            r = consumeSpaces(is);
        if (r == '"') {
            return readStringContent(is);
        } else if (r == '[')
            return new JSONArrayImpl(getEncoding(),is);
        else if (r == '{')
            return new JSONObjectImpl(getEncoding(),is);
        else
            return readNumberOrBooleanOrNull(is, r);
    }

    /**
     * read a number or a boolean or null
     * @param is
     * @param r
     * @return
     * @throws IOException
     * @throws JSONParsingException
     */
    protected Object readNumberOrBooleanOrNull(InputStream is, int r) throws IOException, JSONParsingException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        os.write(r);
        while (true) {
            r = is.read();
            if (r == -1)
                throw new JSONParsingExceptionUnexpectedEndOfStream();
            if (r == ' ' || r == '\t' || r == '\n' || r == '\r') {
                break;
            }
            if (r == ',' || r == '}') {
                next = r;
                break;
            }
            os.write(r);
        }
        String read = os.toString(getEncoding());
        switch (read) {
        case "true":
            return Boolean.TRUE;
        case "false":
            return Boolean.FALSE;
        case "null":
            return null;
        default:
            return parseNumber(read);
        }
    }
    
    private BigDecimal parseNumber(String read) throws JSONParsingExceptionUnexpectedContent {
        Matcher matcher = number.matcher(read);
        if (!matcher.matches()) {
            throw new JSONParsingExceptionUnexpectedContent(read);   
        }
        return new BigDecimal(read);
    }

    enum Object_State {
        INIT, EXPECT_STRING, EXPECT_STRING_OR_END, EXPECT_COLUMN, EXPECT_VALUE, EXPECT_END_OR_NEXT, END
    }
    
    private String getEncoding() {
        return encoding;
    }
    
    private final static String DIGIT19 = "[1-9]";
    private final static String DIGIT = "\\d";
    private final static String P_INT = "("+DIGIT+"{1}|"+DIGIT19+"{1}"+DIGIT+"+)";
    private final static String N_INT = "(-"+DIGIT+"{1}|-"+DIGIT19+"{1}"+DIGIT+"+)";
    private final static String INT = "("+P_INT+"|"+N_INT+")";
    private final static String FRAC = "\\.\\d+";
    private final static String E = "(e|e\\+|e\\-|E|E\\+|E\\-)";
    private final static String EXP = E+"\\d+";
    Pattern number = Pattern.compile("("+INT+"|"+INT+FRAC+"|"+INT+EXP+"|"+INT+FRAC+EXP+")");
    protected Object_State state;
    protected int next;
    
    private String encoding;
}
